fix three plugin crash/UB bugs; test coverage for 14 plugins - #5939
Alistair-Afton wants to merge 17 commits into
Conversation
f3c1610 to
6e64b3a
Compare
583dfb2 to
14ccd5b
Compare
|
Root cause of the recurring Fixed in f42685d: both tests now save/restore |
SilasD
left a comment
There was a problem hiding this comment.
this is not a full review, I will do that on another day as I am running out of steam today.
in general I don't care about correct code in test suites nearly as much as correct code in tools.
934c663 to
610c05d
Compare
|
The Windows empty-config leg failure is root-caused and fixed in a38d479: Fixed by guarding the null civ and actually assigning the intended civ-race fallback. Verified in-game: |
std::stoi throws uncaught on non-numeric input, crashing the game. Use string_to_int and reject invalid ids with CR_WRONG_USAGE.
parameters[1] was read unconditionally when the first parameter was 'set', which is undefined behavior when fewer arguments are given.
MapExtras::Block::biomeIndexAt returns -1 when the biome lookup fails, and biome_by_idx.at(-1) threw std::out_of_range. The exception escaped the plugin command handler and terminated the game. Return NULL like the existing unresolvable-layer path.
Tests must not leak global state to subsequent tests. Restore the prior pause_state instead of forcing unpaused, and restore suspendmanager's persisted preventblocking value instead of hardcoding it.
Trigger real fey/fell/macabre moods per skill and assert the generated job_items match the documented vanilla sets, that BAR and CLOTH demands carry the dimension-corrected quantities (the plugin's vanilla bugfix), and that thread is never requested.
The fallback race lookup dereferenced civ without a null check, so a site whose owner has an undefined race and whose civ_id does not resolve crashed DF (observed as an access violation in CI on the Windows empty-config leg). Assign the civ's race as the intended fallback and only warn when the owner's race is actually undefined.
e672c73 to
f8351ea
Compare
SilasD
left a comment
There was a problem hiding this comment.
I am satisfied with the Lua test harnesses.
I will be happier if some raw writes are changed to supported API calls such as dfhack.maps.getTileFlags() and dwarfmode.setCursorPos().
I left a very long note in test/plugins/autodump.lua which I DO NOT require be implemented; it's rather blue-sky and not worth holding up a merge. (partly I'm curious how the AI handles it.)
| expect.eq(y, item.pos.y) | ||
| expect.eq(z, item.pos.z) | ||
| expect.false_(item.flags.dump) | ||
| expect.true_(item.flags.forbid) |
There was a problem hiding this comment.
TO BE CLEAR, I am NOT insisting that you exercise all or any of these possibliities.
I will NOT hold up a merge over this. any test suite is better than none at all, and I am grateful to have these tests.
this all looks good. another place to test is the map_block the item is in; map_block.items is a sorted vector of item.ids.
it would be best to check that the item ids are removed from the old map_block and inserted into the new map_block on item teleport. including, I suppose, the special case of teleporting to another tile in the same map_block (or even to the item's current tile), which shouldn't change that vector at all.
edit: verifying removal of an item from a map_block is covered below.
edit: it would be good to verify that the map_block.items list remains sorted.
to test this properly, you probably need to find a map_block with a lot of items in it, i.e. #map_block.items has a lot of entries.
edit: there is a tile_occupancy.item flag that should be set on the items' new tile.
edit: item.flags.on_ground should be set, item.flags.in_inventory should be clear, item.flags.in_building should be clear.
| expect.eq(z, item.pos.z) | ||
| expect.false_(item.flags.dump) | ||
| expect.true_(item.flags.forbid) | ||
| end) |
There was a problem hiding this comment.
TO BE CLEAR, I am NOT insisting that you exercise all or any of these possibliities.
I will NOT hold up a merge over this. any test suite is better than none at all, and I am grateful to have these tests.
this test suite doesn't have anything that verifies the item is removed from the old location.
fully exercising that will be rather hard, as there are three four common cases:
the item was on the floor.
- items with
item.flags.constructionset should not be teleported. - items with
item.flags.artifactset should not be teleported. (I am in favor of changing the code to allow it, but that's a different discussion.) - items with
items.flag.forbidset should only be teleported if theforbiddenoption is given. and conversely, items withitems.flags.forbidclear should not be teleported if theforbiddenoption is given. - the item id should be removed from
map_block.itemsunless the item's new tile is in the samemap_block. - the tile's
tile_occupancy.itemflag should be cleared if there are no more items on that tile. I think the best way to determine this is by walking all item ids still in themap_block.itemsvector, getting each item, and testing if theitem.posmatches the tile's pos. if there are any that match, that flag should remain set. - the
map_block.itemsvector should still be sorted.
the item was in a building.
item.posmay not be valid for items in buildings, usedfhack.items.getPosition(item).- items which are in a building and can be removed from that building have
-
item.flags.on_ground == false
-
item.flags.in_inventory == false
-
item.flags.in_building == false
- the item has a
general_ref_building_holderstwith the building's id. - items which are part of a building, i.e.
item.flags.in_building == true, should not be teleported. - a building's
contained_itemsvector is an unsorted list ofbuildingitemstwhich point to the items inside it.
after the teleport: - all item data should be set per your current tests and the previous comment.
- the item should no longer have a
general_ref_building_holderst. - the relevant element of the
building.contained_itemsvector should have been removed. - cages are a special case:
-
- items in a cage are contained in the cage item, not the cage building.
-
- the cage building only has the cage item and and attached mechanisms.
- I think animal traps work the same way for both the bait item and the vermin item, but I am not certain.
the item was in a container.
item.posmay not be valid for items in containers, usedfhack.items.getPosition(item).- items in a container have
item.flags.in_inventory == true,item.flags.on_ground == false,item.flags.in_building == false. - items in a container have a
general_ref_contained_in_itemstwith the container's item id. - the container has a
general_ref_contains_itemstwith the item's id. general_refsvectors are unsorted.
after the teleport:- all item data should be set per your current tests and the previous comment.
- the item should no longer have a
general_ref_contained_in_itemst. - the container should no longer have a
general_ref_contains_itemstwith that item's id. - either the container weight should have changed or
flags.weight_computedshould be clear? don't bother.
the item was in a unit's inventory.
item.posmay not be valid for items in in a unit's inventory, usedfhack.items.getPosition(item).- items in a unit's inventory have
item.flags.in_inventory == true,item.flags.on_ground == false,item.flags.in_building == false, exactly like items in a container. - items in a unit's inventory have a
general_ref_unit_holderstwith the unit's unit id. - the
unit.inventoryvector has aunit_inventory_itemthat points to the item.
after the teleport: - all item data should be set per your current tests and the previous comment.
- the item should no longer have a
general_ref_unit_holderst. - the relevant element of the
unit.inventoryvector should have been removed. - the unit's weight should somehow be flagged as need-to-recompute? don't bother.
there are also rare cases: the item is/was a projectile, the item is uncategorized, possibly others I can't think of. I think these don't need to be tested, as I am not convinced that autodump should even attempt to handle them.
this is all really messy and may not need to be verified for autodump, because autodump uses library calls that handle the messiness. OTOH I don't think we have anything else that verifies those calls.
all of these tests will not be necessary for the destroy-item case, because that relies on the game doing the work.
this got big. please do not feel any obligation here.
| local function set_cursor(x, y, z) | ||
| df.global.cursor:assign{x=x, y=y, z=z} | ||
| end | ||
|
|
||
| local function clear_cursor() | ||
| df.global.cursor:assign{x=-30000, y=-30000, z=-30000} | ||
| end |
There was a problem hiding this comment.
API dwarfmode.setCursorPos and friends per the updates for test/plugins/autodump.lua.
updating is preferable but not necessary.
| df.global.cursor:assign{x=-30000, y=-30000, z=-30000} | ||
| end, function() | ||
| df.global.cursor:assign{x=-30000, y=-30000, z=-30000} |
There was a problem hiding this comment.
API dwarfmode.setCursorPos and friends per the updates for test/plugins/autodump.lua.
| local function count_items_at(x, y, z) | ||
| local n = 0 | ||
| for _, item in ipairs(df.global.world.items.other.IN_PLAY) do | ||
| if item.pos.x == x and item.pos.y == y and item.pos.z == z then |
There was a problem hiding this comment.
item.pos is unreliable; better to use API dfhack.items.getPosition(item)
| expect.eq(before + 1, count_items_at(x, y, z)) | ||
|
|
||
| for _, item in ipairs(df.global.world.items.other.IN_PLAY) do | ||
| if item.pos.x == x and item.pos.y == y and item.pos.z == z |
There was a problem hiding this comment.
item.pos is unreliable; better to use API dfhack.items.getPosition(item)
| end | ||
|
|
||
| local function des_at(x, y, z) | ||
| return dfhack.maps.getTileBlock(x, y, z).designation[x % 16][y % 16] |
There was a problem hiding this comment.
it would probably be better to use dfhack.maps.getTileFlags()
untested code:
return select(1, dfhack.maps.getTileFlags(x, y, z))
| -- every item_type a fey/secretive/possessed mood may ever request | ||
| -- (bars/wafers and cloth are the only dimensional demands; thread is | ||
| -- never requested) | ||
| local ALLOWED_ITEM_TYPES = { | ||
| BOULDER=true, WOOD=true, SKIN_TANNED=true, CLOTH=true, BAR=true, | ||
| ROUGH=true, SMALLGEM=true, BLOCKS=true, NONE=true, | ||
| } | ||
|
|
There was a problem hiding this comment.
Edit: I will NOT delay merging this PR over this request.
you're correct that THREAD is never requested, but I would like you to add it anyway.
Rationale 1: PR #5952 is likely to be merged, and it has code to set a field for THREAD.
Rationale 2: this recent conversation on our Discord mentions that including THREAD is harmless.
Quietust — Yesterday at 1:54 PM
The full set of strange mood item requests are as follows:
Fell: CORPSE
Macabre: REMAINS, CORPSEPIECE of BONE, CORPSEPIECE of SKULL
Other: BOULDER, WOOD, SKIN_TANNED, CLOTH, BAR, ROUGH, CORPSEPIECE of BONE or SHELL
Decorations: REMAINS (macabre only), CORPSEPIECE of BONE, WOOD, BAR, SMALLGEM, BLOCKS, ROUGH, BOULDER, SKIN_TANNED, CLOTHWhich reminds me - there were recent changes to strange moods, and while I'm pretty sure those were restricted to outputs rather than inputs, I really should recheck the mood startup code to make sure the DFHack plugin is still correct.
And I also need to add a comment somewhere in there (ideally at the very top) to indicate which version I last checked.
I'm sure this plugin is one that would probably be eligible for Lua conversion, but that will make it far more difficult for me to maintain.
(and given that I'm probably the primary maintainer of that plugin, I don't know whether it would be worth it in the long run)Dikbutdagrate — Yesterday at 4:03 PM
I was kind of hoping there were additional tweaks to strange moods, which Tarn perhaps maybe neglected to mention with the change to mood item outputs in 53.16 (code is ancient, and could use some spice). Haven't noticed anything yet though.rome of oxtrot — Yesterday at 4:26 PM
it's harmless to include it and there's always the (small) chance that toady will, intentionally or otherwise, add thread as a possible rquested materialQuietust — Yesterday at 5:05 PM
Funny thing: in the code for adding mood decoration material requests, there's code to set min_dimension for BAR and CLOTH and it also handles THREAD.
| elseif item.item_type == df.item_type.CLOTH then | ||
| expect.eq(10000, item.min_dimension) | ||
| expect.eq(0, item.quantity % 10000) | ||
| else |
There was a problem hiding this comment.
if you add THREAD, check its expected item.min_dimension == 15000 here, and the item.quantity as well.
| 'base item for ' .. skill) | ||
| check_dimension(base) | ||
| for _, item in ipairs(job.job_items.elements) do | ||
| expect.ne(df.item_type.THREAD, item.item_type) |
There was a problem hiding this comment.
if you add THREAD, remove this test.
Vanilla's mood decoration request code handles THREAD with a 15000 min_dimension like BAR/CLOTH, and it is a plausible future or rare request type, so allow it in the allowed-types set, assert its dimensional fields, and drop the never-requested assertion.
|
THREAD changes done in ef1c305:
|
Per review: replace raw df.global.cursor writes with dwarfmode.setCursorPos/clearCursorPos, read tile flags via dfhack.maps.getTileFlags, and resolve item positions with dfhack.items.getPosition instead of item.pos. Also strengthen checks: deramp now verifies the z+1 tile is cleared only when it was RampTop (with a fabricated non-RampTop case), and autodump verifies ground/inventory/building flags, occupancy.item on the destination tile, and map_block.items membership/sortedness.
|
All review comments addressed in 1f64b0d (plus ef1c305 for the strangemood THREAD items):
Verified in-game: all 44 tests across the touched files pass (441 checks). The four removal-verification cases in the longer autodump note (building/container/unit-inventory refs) are not implemented — happy to take those as a follow-up if you want them. Also cleaned up stale test pollution in the fort: two units had leftover mood state from an earlier interrupted test run (one with a |
Summary
3dveins: fix a crash when a tile's biome cannot be resolved (biome_by_idx.at(-1)threwstd::out_of_range, which escaped the command handler and terminated the game)strangemood: fix a crash when--idis given a non-numeric value (std::stoithrew uncaught); invalid ids now return a usage errorsuspendmanager: fix an out-of-bounds parameter read whensetis given too few arguments3dveins,autodump,changeitem,changelayer,createitem,deramp,export-world-map,filltraffic,flows,liquids,showmood,strangemood,suspendmanager, andtubefill(57 tests)Test plan